local snow    = require("meet_your_mods.mhr").snow
local via     = require("meet_your_mods.mhr").via
local setting = require("meet_your_mods.setting")
local input   = require("meet_your_mods.input")

local ui = {
    clusters   = {},
    pad_status = {},
    key_status = {},
    children   = {},
    windows    = {},
}

function ui:_find_cluster_(name)
    for _, cluster in pairs(self.clusters) do
        if name == cluster.id then
            return cluster
        end
    end
    return nil
end

function ui:sameline(cluster)
    cluster = self:_find_cluster_(cluster)
    if cluster then
        table.insert(cluster.components, function() imgui.same_line() end)
    end
end

function ui:spacing(cluster)
    cluster = self:_find_cluster_(cluster)
    if cluster then
        table.insert(cluster.components, function() imgui.spacing() end)
    end
end

function ui:static_text(cluster, label, color)
    cluster = self:_find_cluster_(cluster)
    if not cluster then
        return
    end

    table.insert(cluster.components, color
        and function() imgui.text_colored(label, color) end
        or function() imgui.text(label) end)
end

function ui:text(cluster, component, setting_key, color_key)
    cluster = self:_find_cluster_(cluster)
    if not cluster then
        return
    end

    table.insert(cluster.components, color_key
        and function()
            imgui.text_colored(setting[component][setting_key],
                setting[component][color_key])
        end
        or function() imgui.text(setting[component][setting_key]) end)
end

--为需处理交互的控件生成唯一ID
function ui:generate_uuid(data)
    local code = { 'e', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', }
    local uuid = {}

    math.randomseed(tonumber(string.reverse(string.sub(tostring(data or {}), 8)), 16))
    for _ = 1, 32 do
        table.insert(uuid, code[math.random(1, 16)])
    end

    return table.concat(uuid)
end

function ui:checkbox(cluster, component, key, label, on_changed, indent)
    cluster = self:_find_cluster_(cluster)
    if not cluster then
        return
    end

    local uuid = self:generate_uuid()
    local draw = function()
        imgui.push_id(uuid)
        local changed, value = imgui.checkbox(label, setting[component][key])
        imgui.pop_id()
        if changed then
            setting[component][key] = value
            setting:save_setting()
            if on_changed then on_changed() end
        end
    end

    table.insert(cluster.components, indent
        and function() imgui.text("　") imgui.same_line() draw() end
        or draw)
end

function ui:combo(cluster, component, key, label, data, ui, on_changed, indent)
    cluster = self:_find_cluster_(cluster)
    if not cluster then
        return
    end

    local hash = {}
    for index, data in pairs(data) do
        hash[tostring(data)] = index
    end

    local uuid = self:generate_uuid()
    local draw = function()
        imgui.set_next_item_width(-1)
        imgui.push_id(uuid)
        local index          = hash[tostring(setting[component][key])]
        local changed, value = imgui.combo(label, index, ui)
        imgui.pop_id()
        if changed and value ~= index then
            setting[component][key] = data[value]
            setting:save_setting()
            if on_changed then on_changed() end
        end
    end
    table.insert(cluster.components, indent
        and function() imgui.text("　") imgui.same_line() draw() end
        or draw)
end

function ui:slider_float(cluster, component, key, label, min, max, format, on_changed, indent)
    cluster = self:_find_cluster_(cluster)
    if not cluster then
        return
    end

    local uuid = self:generate_uuid()
    local draw = function()
        imgui.push_id(uuid)
        local changed, value = imgui.slider_float(label, setting[component][key], min, max, format)
        imgui.pop_id()
        if changed then
            setting[component][key] = value
            setting:save_setting()
            if on_changed then on_changed() end
        end
    end

    table.insert(cluster.components, indent
        and function() imgui.text("　") imgui.same_line() draw() end
        or draw)
end

function ui:input_text(cluster, component, key, label)
    cluster = self:_find_cluster_(cluster)
    if not cluster then
        return
    end

    local uuid = self:generate_uuid()
    table.insert(cluster.components, function()
        imgui.push_id(uuid)
        local changed, value = imgui.input_text(label, setting[component][key])
        imgui.pop_id()
        if changed then
            setting[component][key] = value
            setting:save_setting()
        end
    end)
end

local function read_pad_input(input_status, component, key)
    if not input_status.enabled then
        return
    end

    local pressed = input:pad_getOn()
    if snow.Pad.Button.NONE == pressed then
        input_status.pressed = snow.Pad.Button.NONE
        input_status.timer   = 0
        return
    end

    if pressed == input_status.pressed then
        input_status.timer = input_status.timer + via.Application.get_FrameTimeMillisecond()
        if input_status.timer >= 1000 then
            input_status.pressed    = snow.Pad.Button.NONE
            input_status.timer      = 0
            input_status.enabled    = false
            setting[component][key] = pressed
            setting:save_setting()
        end
    else
        input_status.pressed    = pressed
        input_status.timer      = 0
        setting[component][key] = pressed
    end
end

local function read_key_input(input_status, component, key)
    if not input_status.enabled then
        return
    end

    local pressed = input:key_getDown()
    if pressed ~= 0 then
        input_status.enabled = false
        setting[component][key] = pressed
        setting:save_setting()
    end
end

function ui:pad_button(cluster, component, key)
    cluster = self:_find_cluster_(cluster)
    if not cluster then
        return
    end

    local input_status = {
        enabled = false,
        pressed = snow.Pad.Button.NONE,
        timer   = 0,
    }

    local uuid = self:generate_uuid(input_status)
    self.pad_status[uuid] = input_status

    table.insert(cluster.components, function()
        read_pad_input(input_status, component, key)

        local pad_label = input:get_pad_label(setting[component][key])
        local label     = not input_status.enabled
            and (pad_label or "None")
            or (pad_label and pad_label .. "+..." or "...")

        imgui.same_line()
        imgui.push_id(uuid)
        if imgui.small_button(label) and not input_status.enabled then
            for _, input_status in pairs(self.pad_status) do
                input_status.enabled = false
                input_status.timer   = 0
                input_status.pressed = snow.Pad.Button.NONE
            end
            input_status.enabled    = true
            input_status.timer      = 0
            input_status.pressed    = snow.Pad.Button.NONE
            setting[component][key] = snow.Pad.Button.NONE
        end
        imgui.pop_id()
    end)
end

function ui:key_button(cluster, component, key)
    cluster = self:_find_cluster_(cluster)
    if not cluster then
        return
    end

    local input_status = {
        enabled = false,
    }

    local uuid = self:generate_uuid(input_status)
    self.key_status[uuid] = input_status

    table.insert(cluster.components, function()
        read_key_input(input_status, component, key)

        local pad_label = input:get_key_label(setting[component][key])
        local label     = not input_status.enabled
            and (pad_label or "None")
            or "..."

        imgui.same_line()
        imgui.push_id(uuid)
        if imgui.small_button(label) and not input_status.enabled then
            for _, input_status in pairs(self.key_status) do
                input_status.enabled = false
            end
            input_status.enabled    = true
            setting[component][key] = 0
        end
        imgui.pop_id()
    end)
end

function ui:_button_(button, cluster, label, delegate, ...)
    cluster = self:_find_cluster_(cluster)
    if not cluster then
        return
    end

    local uuid = self:generate_uuid()
    local parameters = { ... }
    table.insert(cluster.components, function()
        imgui.push_id(uuid)
        if button(label) then
            delegate(table.unpack(parameters))
        end
        imgui.pop_id()
    end)
end

function ui:button(cluster, label, delegate, ...)
    self:_button_(imgui.button, cluster, label, delegate, ...)
end

function ui:small_button(cluster, label, delegate, ...)
    self:_button_(imgui.small_button, cluster, label, delegate, ...)
end

function ui:cluster(id, name)
    if not self:_find_cluster_(id) then
        table.insert(self.clusters, {
            id         = id,
            name       = name,
            components = {},
        })
    end
end

function ui:_window_(list, is_show, on_close, title, draw, ...)
    local parameters = { ... }
    table.insert(list, function()
        if not is_show() then
            return
        end

        if imgui.begin_window(title, true, 0) then
            draw(table.unpack(parameters))
            imgui.end_window()
        else
            on_close()
        end
    end)
end

function ui:child_window(is_show, on_close, title, draw, ...)
    self:_window_(self.children, is_show, on_close, title, draw, ...)
end

function ui:window(is_show, on_close, title, draw, ...)
    self:_window_(self.windows, is_show, on_close, title, draw, ...)
end

function ui:get_font()
    if self.font then
        return self.font
    end

    self.font = imgui.load_font("NotoSansSC-Regular.otf",
        imgui.get_default_font_size() + 6,
        {
            0x0020, 0x00FF, -- Basic Latin + Latin Supplement
            0x2000, 0x206F, -- General Punctuation
            0x3000, 0x30FF, -- CJK Symbols and Punctuations, Hiragana, Katakana
            0x31F0, 0x31FF, -- Katakana Phonetic Extensions
            0x4e00, 0x9FFF, -- CJK Ideograms
            0xFF00, 0xFFEF, -- Half-width characters
            0,
        })

    return self.font
end

function ui:draw_components()
    for _, cluster in pairs(self.clusters) do
        if imgui.tree_node(cluster.name) then
            for _, component in pairs(cluster.components) do
                component()
            end
            imgui.tree_pop()
        end
    end
end

function ui:draw_main()
    imgui.push_font(ui:get_font())
    if imgui.tree_node("Meet your mods") then
        self:draw_components()
        for _, window in pairs(self.children) do
            window()
        end
        imgui.tree_pop()
    end
    imgui.pop_font()
end

function ui:draw_windows()
    imgui.push_font(ui:get_font())
    for _, window in pairs(self.windows) do
        window()
    end
    imgui.pop_font()
end

function ui:start()
    re.on_draw_ui(function()
        self:draw_main()
    end)

    re.on_frame(function()
        self:draw_windows()
    end)
end

return ui
